#include<iostream>
#include<pthread.h>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<thread>//C++封装的线程库
using namespace std;
//学习线程前的知识补充：
//1,32位机器的的寻址方式：32位bit位会被拆分为10+10+12，前两个10位的bit位分别是页目录的地址和二级页表的地址，最后的12位bit位是刚好作为一个页框的大小的偏移量4096
//所以从虚拟地址到物理地址的寻址转化是多级页表加上偏移量的方式，第一个10锁定对应的二级页表的地址，第二个10锁定对应的物理地址的页框的首地址，最后的12个bit位作为偏移量大小锁定最终的物理地址的位置

//2,线程比进程更加轻量化：因为进程切换需要把CPU中的缓存cache的值（也就是热数据（访问频率较高的数据））清空，但是线程的切换只需要切换上下文数据即可，不用清理热数据



//正式开始学习线程：线程的实现方法在linux和windows不同，linux把线程设定为轻量级的进程，所以继续使用了进程的pcb结构，使得线程的实现更为简洁，windows是真正的实现了线程对应的结构

//线程之间的资源是共享的，包括堆区，全局变量，代码等内容，但是每个线程都有自己的局部存储区，和独立栈（也就是线程在自己的函数代码段里创建的栈区的变量）

string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex,sizeof(hex),"%p",tid);//以地址的方式打印就是转为16进制
    return hex;//字符数组的首地址是字符首元素的地址，隐式类型转换为string类型
}

void* threadRun(void *args)//线程执行的函数必须是void*为返回值，void*为参数的函数，这是规定
{
    //void *args接收的就是pthread_create的最后一个参数传进来的数据，我们使用void *指针接收就要强转一下：
    const char*pc=static_cast<const char*>(args);//static_cast<要强转的类型>要强转的目标
    cout<<"i am a thread:"<<pc<<endl;
    sleep(5);
    cout<<"thread quit"<<endl;
    return nullptr;
}

//先熟悉一些基本的线程库的接口：
void test1()
{
    pthread_t tid;//pthread_t 是库内置的数据类型，描述线程的id，是作为输出型参数

    const char*c="thread 1";
    pthread_create(&tid,nullptr,threadRun,(void *)c);//最后一个参数是传递个线程执行函数的数据，必须是地址类型
    //注意：这里一定要显式强转为void*类型才能传参，否则译器会报错，因为它不允许隐式地移除 const 修饰符。为了解决这个问题，你需要显式地进行类型转换，告诉编译器你知道自己正在做什么，并且愿意承担由此可能引起的后果。
    //const指针修饰的对象不能隐式类型转换为其他指针类型，这在编译器看来是不允许的，其他类型的指针可以再传参的时候隐式类型转换

    //所有的线程都是同一个进程的不同的执行流，也可以看做是访问同一片空间的多个轻量级进程，最开始的进程我们称为主线程
    cout<<"i am a mian thread"<<"the new thread id is:"<<toHex(tid)<<endl;
    
    //主线程也要对子线程进行回收的工作，所以主线程要最后退出：join是阻塞等待回收的线程

    pthread_join(tid,nullptr);//对tid线程进行回收，不关心状态就置nullpter
    cout<<"thread join"<<endl;
}

void threadrun()
{
    for(int i=0;i<5;i++)
    {
        cout<<"i am a new thread"<<endl;
        sleep(1);
    }
}
//C++已经支持多线程了，我们的pthread库是原生线程库，C++中的线程库是对原生线程库的封装
void test2()
{
    //创建线程
    thread t1(threadrun);//只需要传递线程要执行的函数即可
    //释放线程：
    t1.join();//join是阻塞等待的
    cout<<"join success"<<endl;
}

//给线程传递的数据大多数时候我们传递的是一个对象，这个对象封装了所有的我们要传递给线程的数据：
class Request
{
public:
    Request(int start,int end,const string &threadname)
    :start_(start),end_(end),threadname_(threadname)
    {}
int start_;
int end_;
string threadname_;

};
class Response
{
public:
    Response(int result,int exitcode):result_(result),exitcode_(exitcode)
    {}
    int result_;//计算结果
    int exitcode_;//计算是否可靠，也就是退出码
};
//让线程执行计算从start到end的值之和：
void* sumCount(void *args)//传递给线程的数据，大部分时候我们传递的是封装的对象
{
    Request*rq=static_cast<Request*>(args);//强转为对象指针从而使用该对象内部的数据
    Response *rsp=new Response(0,0);
    for(int i=rq->start_;i<=rq->end_;i++)
    {
        cout<<"thread is running,calling..."<<i<<endl;
        rsp->result_+=i;//计算累计的和结果放在rsp指向的对象中
        usleep(100000);
    }
    delete rq;//线程之间的资源是共享的，所以主线程在堆区申请的空间可以让子线程释放

    return rsp;//rsp会被隐式转换为void*指针返回，该值会被传递给join的输出型参数

}

//注意：线程的执行函数的返回值会作为状态数据以void*类型传递给join的输出型参数retval
void test3()
{
    pthread_t tid;
    Request*rq=new Request(1,100,"thread 1");
    pthread_create(&tid,nullptr,sumCount,rq);//这里rq可以隐式类型转换为void *传参，在函数内部使用的时候要强转回去使用即可

    void *ret;
    pthread_join(tid,&ret);//第二个参数是输出型参数，需要传递一个void *指针的地址，因为要修改指针的值，在内部要使用二级指针来修改
    //因为要接收我们在线程执行函数中返回的void *变量

    Response*rsp=static_cast<Response*>(ret);
    //输出ret指针指向的response结构体中保存的内容：
    cout<<"rsp->result:"<<rsp->result_<<","<<"exitcode:"<<rsp->exitcode_<<endl;
    
    //释放子进程申请的空间：
    delete rsp;
}

//定义一个全局变量：
int g_val=100;//每个线程都可以直接访问这个全局变量，并且访问的就是同一个全局变量，这是属于线程共享的资源

//那如果想要每个线程看到独属于自己的全局变量呢？使用__thread就可以把声明的数据放在线程的局部存储中，每个线程都可以得到一份相对独立的数据
__thread int g_val2=100;//线程的局部存储技术，只有用在内置类型，不能用在自定义类型

void *threadRoutine(void *args)
{
    //detach分离线程：分离线程在结束时会自动释放其占用的资源，包括线程的栈和线程控制块，因此不需要调用 pthread_join 来等待线程结束。
    pthread_detach(pthread_self());//pthread_self返回自己的线程标识符
    //注意：detach分离线程可以在主线程中分离也可以在子线程中自己分离


    g_val2+=100;//此时访问的全局变量是自己局部存储空间的局部变量
    cout<<g_val2<<&g_val2<<endl;
    sleep(1);
    return nullptr;
}

void test4()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRoutine,nullptr);
    sleep(1);
    g_val2-=100;
    cout<<g_val2<<&g_val2<<endl;
}

//exit是用来退出进程的，不能用在线程中

//只要有一个线程出错，所有的线程都会崩溃

//不同的线程可以执行同一个执行函数，这就是该函数被重入了

//补充一个不常用的接口：pthread_cancel 是 POSIX 线程库中的一个函数，用于请求取消一个线程。
//pthread_exit:当一个线程调用 pthread_exit 时，它将清理线程的资源并终止执行。void pthread_exit(void *value_ptr);value_ptr：这是一个指向线程退出状态的指针。当主线程或其他线程使用 pthread_join 等待这个线程结束时，这个值会被传递给 pthread_join 的 retval 参数。

int main()
{
    test4();

    return 0;
}

//补充一个概念：死锁：
/*
死锁（Deadlock）是多线程编程和操作系统中的一种情况，其中两个或多个进程（或线程）在执行过程中，因争夺资源而造成的一种僵局。当发生死锁时，每个进程都在等待其他进程释放资源，但没有进程能够向前推进，因为它们都需要对方所持有的资源才能继续执行。这种状态会导致程序无法继续正常运行。

死锁通常涉及四个条件，这四个条件同时满足时就可能发生死锁，它们被称为死锁的四个基本条件：

互斥条件（Mutual Exclusion）：

资源不能被多个进程同时使用，即一个资源在某一时刻只能由一个进程占用。
占有和等待条件（Hold and Wait）：

一个进程至少占有一个资源，并且等待获取其他进程所占有的资源。
不可抢占条件（No Preemption）：

资源只能由占有它的进程自愿释放，其他进程不能强行抢占。
循环等待条件（Circular Wait）：

存在一个进程序列，使得每个进程都等待下一个进程所占有的资源。
死锁的预防和避免
破坏互斥条件：

允许资源被多个进程同时使用，但这在很多情况下不现实。
破坏占有和等待条件：

要求进程在请求新资源前释放已占有的所有资源。
破坏不可抢占条件：

允许进程抢占其他进程的资源，但这可能导致新的复杂性。
破坏循环等待条件：

对资源进行排序，要求进程按照一定的顺序请求资源。
使用银行家算法：

在动态分配资源前，预先检查是否会导致不安全状态。
死锁的检测和恢复
死锁检测：

系统周期性地检查是否存在死锁。
资源分配图：

使用资源分配图来检测循环等待条件。
恢复措施：

一旦检测到死锁，可以采取恢复措施，如：
终止进程：终止参与死锁的进程。
回滚：撤销进程执行的部分操作，释放资源。
资源剥夺：从进程中夺取资源分配给其他进程。
*/